package com.it.alg;

import java.io.PrintStream;

import com.it.io.IOSequence;
import com.it.io.IOSequenceFactory;

public abstract class DupAlgorithmTemplate implements DupAlgorithm {
	
	protected Conf conf;
	
	public DupAlgorithmTemplate(Conf conf) {
		this.conf = conf;
	}
	
	@Override
	public final <S, E> S execute(IOSequenceFactory<S, E> ioSequenceFactory, S input) {
		IOSequence<S, E> ioSequence = ioSequenceFactory.getIOSequence();
		ioSequence.initInput(input);
		var stack = new Stack<S, E>();
		while (ioSequence.hasInput()) {
			E value = ioSequence.input();
			stack.add(value);
		}
		conf.out.println("Input: " + stack.nodesToString(conf.separator));
		conf.out.println("Output:");
		while (!stack.isAllPushed()) {
			push(ioSequenceFactory, stack);
		}
		
		ioSequence.initOutput(stack.size());
		for (E value : stack) {
			ioSequence.output(value);
		}
		return ioSequence.finish();
	}

	private <E, S> void elimicateIfNeed(IOSequenceFactory<S, E> ioSequenceFactory, Stack<S, E> stack, NodeInfo<E> pushedNode) {
		int repeatCount = pushedNode.getRepeatCount();
		if (repeatCount == conf.repeatNum) {
			for (int i = 0; i < conf.repeatNum; i++) {
				pop(ioSequenceFactory, stack);
			}
			replace(ioSequenceFactory, repeatCount, pushedNode.getValue(), stack);
			printStepResult(ioSequenceFactory, repeatCount, pushedNode.getValue(), stack);
		}
	}

	protected abstract <S, E> void replace(IOSequenceFactory<S, E> ioSequenceFactory, int repeatCount, E value, Stack<S, E> stack);

	protected abstract <S, E> String getStepRemark(IOSequenceFactory<S, E> ioSequenceFactory, int repeatCount, E value, Stack<S, E> stack, String separator);
	
	protected final <S, E> NodeInfo<E> push(IOSequenceFactory<S, E> ioSequenceFactory, Stack<S, E> stack) {
		NodeInfo<E> pushedNode = stack.push();
		if (conf.needStackChange) {
			conf.out.println("push:" + pushedNode.getValue());
		}
		elimicateIfNeed(ioSequenceFactory, stack, pushedNode);
		return pushedNode;
	}
	
	protected final <S, E> NodeInfo<E> push(IOSequenceFactory<S, E> ioSequenceFactory, Stack<S, E> stack, E value) {
		NodeInfo<E> pushedNode = stack.push(value);
		if (conf.needStackChange) {
			conf.out.println("push:" + pushedNode.getValue());
		}
		elimicateIfNeed(ioSequenceFactory, stack, pushedNode);
		return pushedNode;
	}
	
	protected final <E, S> void pop(IOSequenceFactory<S, E> ioSequenceFactory, Stack<S, E> stack) {
		NodeInfo<E> popedNode = stack.pop();
		if (conf.needStackChange) {
			conf.out.println("pop:" + popedNode.getValue());
		}
	}
	
	protected <S, E> void printStepResult(IOSequenceFactory<S, E> ioSequenceFactory, int repeatCount, E value, Stack<S, E> stack) {
		if (conf.needStepResult) {
			String stepRemark = this.getStepRemark(ioSequenceFactory, repeatCount, value, stack, conf.separator);
			if (stepRemark != null && stepRemark.length() != 0) {
				conf.out.println("-> " + stack.nodesToString(conf.separator) + ", " + stepRemark);
			} else {
				conf.out.println("-> " + stack.nodesToString(conf.separator));
			}
		}
	}
	
	public static abstract class Conf {
		int repeatNum;
		boolean needStepResult;
		boolean needStackChange;
		PrintStream out;
		String separator;
		public Conf() {
			this(3, true, false, System.out, "");
		}
		public Conf(int repeatNum, boolean needStepResult, boolean needStackChange, PrintStream out, String separator) {
			if (repeatNum <= 2) {
				throw new IllegalArgumentException("repeat number must be grater than 1");
			}
			this.repeatNum = repeatNum;
			this.needStepResult = needStepResult;
			this.needStackChange = needStackChange;
			this.out = out;
			this.separator = separator;
		}
	}
}